﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nh = NHibernate;
using NHibernate.Linq;
using FoundationLib.Data.NHibernate.Configuration;
using NHibernate.Context;
using NHibernate.Cfg;
using System.Reflection;
using System.Diagnostics.Contracts;
using FoundationLib.Data;
using FoundationLib.Ifc.Data;
using FoundationLib.Ifc.Exceptions;

namespace FoundationLib.Data.NHibernate
{
	/// <summary>
	/// This class serves as a session manager for NHibernate.
	/// </summary>
	public class NHibernateSessionManager : ISessionManager
	{
		/// <summary>
		/// The NHibernate session factory, which should only be created ONCE!
		/// </summary>
		private Nh.ISessionFactory sessionFactory;
		/// <summary>
		/// The NHibernate session manager configuration
		/// </summary>
		private NHibernateConfigSection configuration;

		/// <see cref="ISessionManager.Session"/>
		public ISession Session
		{
			get
			{
				return this.Bind();
			}
		}

		/// <summary>
		/// Creates a new <see cref="NHibernateManager"/> instance.
		/// </summary>
		public NHibernateSessionManager()
		{
			this.configuration = this.ReadConfiguration();
		}

		/// <summary>
		/// Reads the NHibernate configuration from the App.config or Web.config file.
		/// </summary>
		/// <remarks>Override this method in derived classes if a custom implementation is needed.</remarks>
		/// <returns>The NHibernate configuration section</returns>
		private NHibernateConfigSection ReadConfiguration()
		{
			//Read configuration
			try
			{
				NHibernateConfigSection configuration = NHibernateConfigSection.FromConfiguration();
				if (configuration == null)
					throw new DataAccessException("Failed to read configuration.");
				return configuration;
			}
			catch (DataAccessException)
			{
				throw;
			}
			catch (Exception ex)
			{
				throw new DataAccessException("Failed to read configuration.", ex);
			}
		}

		/// <summary>
		/// Creates the session factory and binds a new session.
		/// </summary>
		private void Initialise()
		{
			// Do not initialise if a session factory already exists
			if (this.sessionFactory != null)
				return;
			try
			{
				NHibernateConfigSection section = this.ReadConfiguration();
				Contract.Assert(section != null && section.File != null && !String.IsNullOrWhiteSpace(section.File.Resource));
				
				if (!String.IsNullOrWhiteSpace(section.File.Assembly))
				{
					AssemblyName assemblyName = new AssemblyName(section.File.Assembly);
					this.sessionFactory = new Nh.Cfg.Configuration().Configure(Assembly.Load(assemblyName), 
						section.File.Resource).BuildSessionFactory();
				}
				else
				{
					this.sessionFactory = new Nh.Cfg.Configuration().Configure(section.File.Resource).BuildSessionFactory();
				}

				Nh.ISession session = this.sessionFactory.OpenSession();
				CurrentSessionContext.Bind(session);
			}
			catch (Exception ex)
			{
				throw new DataAccessException("Failed to initialise NHibernate.", ex);
			}
		}

		/// <see cref="ISessionManager.Bind()"/>
		public ISession Bind()
		{
			if (this.sessionFactory == null)
				this.Initialise();
			if (!CurrentSessionContext.HasBind(this.sessionFactory))
			{
				Nh.ISession session = this.sessionFactory.OpenSession();
				CurrentSessionContext.Bind(session);
				return new NHibernateSessionProxy(session);
			}
			else
			{
				return new NHibernateSessionProxy(this.sessionFactory.GetCurrentSession());
			}
		}

		/// <see cref="ISessionManager.Unbind()"/>
		public ISession Unbind()
		{
			if (this.sessionFactory == null || !CurrentSessionContext.HasBind(this.sessionFactory))
				return null;
			Nh.ISession session = CurrentSessionContext.Unbind(this.sessionFactory);
			return new NHibernateSessionProxy(session);
		}

		/// <see cref="ISessionManager.Close()"/>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
		public void Close()
		{
			ISession session = this.Unbind();
			if (session == null)
				return;

			session.Close();
		}

		/// <summary>
		/// Closes the current session factory and frees all resources.
		/// </summary>
		private void CloseSessionFactory()
		{
			if (this.sessionFactory != null)
			{
				try
				{
					this.Close();
				}
				catch (Exception) {}
				this.sessionFactory.Close();
				this.sessionFactory.Dispose();
				this.sessionFactory = null;
			}
		}

		/// <summary>
		/// Disposes of this instance and releases managed resources.
		/// </summary>
		/// <param name="disposing">True if managed resources are to be released</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				this.CloseSessionFactory();
			}
		}

		/// <see cref="IDisposable.Dispose()"/>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}
	}
}
